module sfifo
  (
   clk,
   data_in,
   data_out,
   empty_n, empty,
   full_n,  full,
   rd_en,
   rst_n,
   wr_en,
   almost_full_n,      //
   almost_empty_n,      //
   cnt                  //
   );

`include "common_funcs.vh"

   parameter      DEPTH                     = 4;
   parameter      WIDTH                     = 8;
   parameter      ALMOST_FULL_THRESHOLD     = 0;
   parameter      ALMOST_EMPTY_THRESHOLD    = 0;
   parameter      FLOP_DATA_OUT             = 0; // non-nil mean data_out is flopped,
                                                 // otherwise, data_out is combinational output

   localparam     BADDR                     = log2(DEPTH);
   localparam     CNT_WIDTH                 = log2_cnt(DEPTH);

   input                        clk;
   input    [WIDTH-1:0]         data_in;
   input                        rd_en;
   input                        rst_n;
   input                        wr_en;
   output   [WIDTH-1:0]         data_out;
   output                       empty_n, empty;
   output                       full_n, full;
   output                       almost_full_n;
   output                       almost_empty_n;
   output   [CNT_WIDTH-1:0]     cnt ;  

   wire                         almost_full_n;
   wire                         almost_empty_n;
   wire     [CNT_WIDTH-1:0]     cnt;
   wire                         empty_n, empty  ;
   wire                         full_n,  full   ;

   wire                         clk      ;
   wire     [WIDTH-1:0]         data_in,data_out_c  ;
   reg      [WIDTH-1:0]         data_out,data_out_d ;
   wire                         rd_en    ;
   wire                         rst_n    ;
   wire                         wr_en    ;


    reg	    [BADDR-1:0]	        rd_ptr,wr_ptr;
    reg	    [CNT_WIDTH-1:0]	    status_cnt;
    reg	    [WIDTH-1:0]	        ram_data;


assign  cnt[CNT_WIDTH-1:0] = status_cnt[CNT_WIDTH-1:0]; 

assign	empty   = cnt == 0 ? 1'b1 : 1'b0;
assign  empty_n = ~empty;
assign	full    = cnt == DEPTH ? 1'b1 : 1'b0;
assign  full_n  = ~full_n;

assign  almost_full_n = cnt[CNT_WIDTH-1:0] > ( {(CNT_WIDTH){1'b1}} - ALMOST_FULL_THRESHOLD)  ? 1'b0 : 1'b1;
assign  almost_empty_n= cnt[CNT_WIDTH-1:0] < ( {(CNT_WIDTH){1'b0}} + ALMOST_EMPTY_THRESHOLD) ? 1'b0 : 1'b1;

always @(posedge clk or negedge rst_n)begin
	if(!rst_n)
		status_cnt[CNT_WIDTH-1:0] <= 0;
    else begin
        case({wr_en,rd_en})
            2'b00:  status_cnt[CNT_WIDTH:0] <= status_cnt[CNT_WIDTH-1:0];
            2'b01:
                if(status_cnt[CNT_WIDTH-1:0] != 0)
		            status_cnt[CNT_WIDTH:0] <= status_cnt[CNT_WIDTH-1:0] - 1'b1;
            2'b10:
	            if(status_cnt[CNT_WIDTH-1:0] != DEPTH)
		            status_cnt[CNT_WIDTH:0] <= status_cnt[CNT_WIDTH-1:0] + 1'b1;
            2'b11:  status_cnt[CNT_WIDTH:0] <= status_cnt[CNT_WIDTH-1:0];
        endcase
    end
end


always @(posedge clk or negedge rst_n)begin
	if(!rst_n)
		wr_ptr <= 0;
	else if(wr_en && full_n)
		wr_ptr <= wr_ptr + 1;
end

always @(posedge clk or negedge rst_n)begin
	if(!rst_n)
		rd_ptr <= 0;
	else if(rd_en && empty_n)
		rd_ptr <= rd_ptr + 1;
end


integer	i;
reg	[WIDTH-1:0]	register	[DEPTH-1:0];

always @(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		for(i=0;i<DEPTH;i=i+1)
			register[i] <= 0;
	end
    else if(wr_en && full_n)begin
        for(i=0;i<DEPTH;i=i+1)  
		    register[i] <= i==wr_ptr ? data_in[WIDTH-1:0] : register[i];
    end
end

assign data_out_c[WIDTH-1:0] = register[rd_ptr];

always @(posedge clk or negedge rst_n)begin
	if(!rst_n)
		data_out_d[WIDTH-1:0] <= 0;
	else if(rd_en && empty_n)
		data_out_d[WIDTH-1:0] <= register[rd_ptr];
end

assign data_out[WIDTH-1:0] = FLOP_DATA_OUT ? data_out_d[WIDTH-1:0] : data_out_c[WIDTH-1:0];

endmodule

